import * as vscode from "vscode";
import * as mime from "mime";
import { readFileSync } from "fs";
import {
  S3Client,
  PutObjectCommand,
  GetObjectCommand,
} from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

import { locale as $l } from "./utils";

class S3 implements Upload {
  config: Config;
  s3Client: S3Client;

  constructor(config: Config) {
    this.config = config;
    const { endpoint, region, accessKeyId, secretAccessKey, config: cfg } = this.config.s3;
    this.s3Client = new S3Client({
      ...cfg,
      endpoint: endpoint,
      region: region,
      credentials: {
        accessKeyId: accessKeyId,
        secretAccessKey: secretAccessKey,
      },
    });
  }

  async getSavePath(filePath: string) {
    return filePath;
  }

  async reconfig(config: Config) {
    this.config = config;
  }

  sanitizeURL = (url: string): string => {
    /*
     * preSignedURLs generated by @aws-sdk are for sharing resources only for a limited time
     * but we are going to get the permanent URL by removing the authorization information
     * from the URL. This will give us the direct link to the image in S3 bucket.
     * Another way would be concatenating the bucket name with region, host and
     * image key but it is not reliable if we are going to support multiple S3
     * compatible providers.
     */
    const decodedURL = decodeURIComponent(url);
    const urlObject = new URL(decodedURL);
    urlObject.search = "";
    urlObject.hash = "";
    return urlObject.toString();
  };

  async upload(filePath: string, savePath: string): Promise<string | null> {
    try {
      await this.doUpload(filePath, savePath);

      const { bucketName } = this.config.s3;
      const getObjectCommand = new GetObjectCommand({
        Bucket: bucketName,
        Key: savePath,
      });
      const encodedURL = await getSignedUrl(this.s3Client, getObjectCommand);

      if (!this.config.s3.cdn) {
        return this.sanitizeURL(encodedURL);
      }

      const decodedURL = decodeURIComponent(encodedURL);
      const urlObject = new URL(decodedURL);

      return this.config.s3.cdn
        .replace(/\${bucket}/g, this.config.s3.bucketName)
        .replace(/\${region}/g, this.config.s3.region)
        .replace(/\${pathname}/g, urlObject.pathname)
        .replace(/\${filepath}/g, savePath);
    } catch (error) {
      let e = error as Error;
      vscode.window.showInformationMessage(
        `${$l["upload_failed"]}${e.message}\n${e.stack}`
      );
      return null;
    }
  }

  doUpload(filePath: string, savePath: string): Promise<any> {
    const body = readFileSync(filePath);

    const { bucketName } = this.config.s3;
    const putObjectCommand = new PutObjectCommand({
      Bucket: bucketName,
      Body: body,
      Key: savePath,
      ContentType: mime.getType(filePath) || "application/octet-stream",
    });

    return this.s3Client.send(putObjectCommand);
  }
}

export default S3;
